// Package main cert-deploy-iis 主程序
package main

import (
    "context"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "log/slog"
    "os"
    "strings"
    "time"

	"github.com/spf13/cobra"

	"github.com/cnssl/cert-deploy-iis/internal/config"
	"github.com/cnssl/cert-deploy-iis/internal/daemon"
	"github.com/cnssl/cert-deploy-iis/internal/deployer"
	"github.com/cnssl/cert-deploy-iis/internal/fetcher"
	"github.com/cnssl/cert-deploy-iis/internal/scanner"
	"github.com/cnssl/cert-deploy-iis/internal/validator"
)

var (
	// 全局变量
	cfgManager *config.Manager
	logger     *slog.Logger

	// 命令行标志
	siteName string
	hostname string
	referID  string
	apiURL   string
	port     int
	ipAddr   string
    force    bool

    // 私钥/CSR 生成参数
    keyType  string
    keySize  int
    keyCurve string
    csrOrg   string
    csrC     string
    csrST    string
    csrL     string
    csrEmail string
)

// 通过 ldflags 注入版本号，例如：-ldflags "-X main.version=v2.0.1"
var version = "dev"

func main() {
	// 获取可执行文件路径
	execPath, err := os.Executable()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to get executable path: %v\n", err)
		os.Exit(1)
	}

	// 初始化配置管理器
	cfgManager, err = config.NewManager(execPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to initialize config manager: %v\n", err)
		os.Exit(1)
	}

	// 初始化日志
	logger = setupLogger()

    // 创建根命令
    rootCmd := &cobra.Command{
        Use:   "cert-deploy-iis",
        Short: "Automatic certificate deployment tool for IIS",
        Long:  "A lightweight tool to automatically deploy certificates from API to IIS servers",
    }

	// 添加子命令
	rootCmd.AddCommand(scanCmd())
	rootCmd.AddCommand(initCmd())
	rootCmd.AddCommand(deployCmd())
	rootCmd.AddCommand(statusCmd())
	rootCmd.AddCommand(checkCmd())
	rootCmd.AddCommand(daemonCmd())
    rootCmd.AddCommand(versionCmd())
    rootCmd.AddCommand(configCmd())

	// 执行命令
	if err := rootCmd.Execute(); err != nil {
		os.Exit(1)
	}
}

// scanCmd 扫描命令
func scanCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "scan",
		Short: "Scan IIS configuration",
		Long:  "Scan IIS to discover SSL sites and their certificate information",
		RunE:  runScan,
	}

	return cmd
}

// initCmd 初始化命令
func initCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "init",
		Short: "Initialize site configuration",
		Long:  "Create a new site configuration file with the specified settings",
		RunE:  runInit,
	}

	cmd.Flags().StringVar(&siteName, "site", "", "IIS site name (required)")
	cmd.Flags().StringVar(&hostname, "hostname", "", "Hostname for SSL binding (required)")
	cmd.Flags().StringVar(&referID, "refer-id", "", "API refer ID (required)")
	cmd.Flags().StringVar(&apiURL, "api-url", "", "API URL (required)")
	cmd.Flags().IntVar(&port, "port", 443, "HTTPS port")
	cmd.Flags().StringVar(&ipAddr, "ip", "*", "IP address for binding")
    // Key/CSR flags
    cmd.Flags().StringVar(&keyType, "key-type", "rsa", "Key type: rsa|ecdsa (default rsa)")
    cmd.Flags().IntVar(&keySize, "key-size", 2048, "RSA key size: 2048|4096 (default 2048)")
    cmd.Flags().StringVar(&keyCurve, "key-curve", "prime256v1", "ECDSA curve: prime256v1|secp384r1|secp521r1")
    cmd.Flags().StringVar(&csrOrg, "csr-org", "", "CSR Organization (O)")
    cmd.Flags().StringVar(&csrC, "csr-c", "", "CSR Country (C)")
    cmd.Flags().StringVar(&csrST, "csr-st", "", "CSR State/Province (ST)")
    cmd.Flags().StringVar(&csrL, "csr-l", "", "CSR Locality/City (L)")
    cmd.Flags().StringVar(&csrEmail, "csr-email", "", "CSR Email")

    cmd.MarkFlagRequired("site")
	cmd.MarkFlagRequired("hostname")
	cmd.MarkFlagRequired("refer-id")
	cmd.MarkFlagRequired("api-url")

	return cmd
}

// deployCmd 部署命令
func deployCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "deploy",
		Short: "Deploy certificate to site",
		Long:  "Fetch certificate from API and deploy to the specified IIS site",
		RunE:  runDeploy,
	}

	cmd.Flags().StringVar(&siteName, "site", "", "Site name (required)")
	cmd.Flags().BoolVar(&force, "force", false, "Force deployment even if not needed")
	cmd.MarkFlagRequired("site")

	return cmd
}

// statusCmd 状态命令
func statusCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "status",
		Short: "Show status of all sites",
		Long:  "Show the status of all configured sites",
		RunE:  runStatus,
	}

	return cmd
}

// checkCmd 检查命令
func checkCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "check",
		Short: "Check certificate expiration",
		Long:  "Check certificate expiration for all configured sites",
		RunE:  runCheck,
	}

	return cmd
}

// daemonCmd 守护进程命令
func daemonCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "daemon",
		Short: "Run in daemon mode",
		Long:  "Run in background and automatically check/deploy certificates",
		RunE:  runDaemon,
	}

	return cmd
}

// versionCmd 版本命令
func versionCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "version",
        Short: "Show version information",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("cert-deploy-iis %s\n", version)
            fmt.Println("Windows IIS certificate auto-deployment tool")
        },
    }

    return cmd
}

// config 命令
func configCmd() *cobra.Command { cmd := &cobra.Command{Use: "config", Short: "Manage site config"}; cmd.AddCommand(configSetCmd()); return cmd }
func configSetCmd() *cobra.Command {
    var site string
    var setKeyType string
    var setKeySize int
    var setKeyCurve string
    var setOrg, setC, setST, setL, setEmail string
    c := &cobra.Command{ Use: "set", Short: "Set key/csr options for a site",
        RunE: func(cmd *cobra.Command, args []string) error {
            if site == "" { return fmt.Errorf("--site is required") }
            sc, err := cfgManager.LoadSite(site); if err != nil { return fmt.Errorf("load site failed: %w", err) }
            if setKeyType != "" { sc.Key.Type = setKeyType }
            if setKeySize != 0 { sc.Key.Size = setKeySize }
            if setKeyCurve != "" { sc.Key.Curve = setKeyCurve }
            if setOrg != "" { sc.CSR.Organization = setOrg }
            if setC != "" { sc.CSR.Country = setC }
            if setST != "" { sc.CSR.State = setST }
            if setL != "" { sc.CSR.Locality = setL }
            if setEmail != "" { sc.CSR.Email = setEmail }
            if err := cfgManager.SaveSite(sc); err != nil { return fmt.Errorf("save site failed: %w", err) }
            fmt.Println("✓ Config updated for", site); return nil }, }
    c.Flags().StringVar(&site, "site", "", "Site name (required)")
    c.Flags().StringVar(&setKeyType, "key-type", "", "rsa|ecdsa")
    c.Flags().IntVar(&setKeySize, "key-size", 0, "RSA key size: 2048|4096")
    c.Flags().StringVar(&setKeyCurve, "key-curve", "", "ECDSA curve")
    c.Flags().StringVar(&setOrg, "csr-org", "", "CSR Organization (O)")
    c.Flags().StringVar(&setC, "csr-c", "", "CSR Country (C)")
    c.Flags().StringVar(&setST, "csr-st", "", "CSR State/Province (ST)")
    c.Flags().StringVar(&setL, "csr-l", "", "CSR Locality/City (L)")
    c.Flags().StringVar(&setEmail, "csr-email", "", "CSR Email")
    return c }

// runScan 执行扫描
func runScan(cmd *cobra.Command, args []string) error {
	logger.Info("scanning IIS sites")

	s := scanner.NewIISScanner()
	sites, err := s.Scan()
	if err != nil {
		return fmt.Errorf("scan failed: %w", err)
	}

	s.PrintScanResult(sites)

	logger.Info("scan completed", "sites_found", len(sites))
	return nil
}

// runInit 执行初始化
func runInit(cmd *cobra.Command, args []string) error {
	logger.Info("initializing site configuration", "site", siteName)

	// 检查站点是否已存在
	if cfgManager.SiteExists(siteName) {
		return fmt.Errorf("site configuration already exists: %s", siteName)
	}

	// 扫描 IIS 获取站点信息
	s := scanner.NewIISScanner()
	siteInfo, err := s.GetSiteByName(siteName)
	if err != nil {
		logger.Warn("site not found in IIS, using provided values", "site", siteName)
		siteInfo = &scanner.IISSiteInfo{
			SiteName: siteName,
			HostName: hostname,
			Port:     port,
			IP:       ipAddr,
		}
	}

	// 创建默认配置
	siteConfig := &config.SiteConfig{
		Version:    "1.0",
		SiteName:   siteName,
		Enabled:    true,
		ServerType: "iis",
		API: config.APIConfig{
			URL:     apiURL,
			ReferID: referID,
		},
		Domains: []string{hostname},
		IIS: &config.IISConfig{
			SiteName: siteInfo.SiteName,
			Hostname: hostname,
			Port:     siteInfo.Port,
			IP:       siteInfo.IP,
		},
		Schedule: config.ScheduleConfig{
			CheckIntervalHours: 12,
			RenewBeforeDays:    15,
		},
		Validation: config.ValidationConfig{
			VerifyDomain:         true,
			TestHTTPS:            false,
			TestURL:              fmt.Sprintf("https://%s", hostname),
			IgnoreDomainMismatch: false,
		},
		Backup: config.BackupConfig{
			Enabled:      true,
			KeepVersions: 3,
		},
        Metadata: config.MetadataConfig{
            CreatedAt:   time.Now(),
            AutoScanned: false,
        },
        Key: config.KeyConfig{Type: keyType, Size: keySize, Curve: keyCurve},
        CSR: config.CSRConfig{Organization: csrOrg, Country: csrC, State: csrST, Locality: csrL, Email: csrEmail},
    }

	// 保存配置
	if err := cfgManager.SaveSite(siteConfig); err != nil {
		return fmt.Errorf("failed to save configuration: %w", err)
	}

	logger.Info("site configuration created",
		"site", siteName,
		"config_file", cfgManager.GetSitesDir()+"/"+siteName+".json")

	fmt.Printf("✓ Created configuration for %s\n", siteName)
	fmt.Printf("  Config file: %s/%s.json\n", cfgManager.GetSitesDir(), siteName)
	fmt.Printf("\nNext steps:\n")
	fmt.Printf("  1. Edit the configuration file to match your setup\n")
	fmt.Printf("  2. Run 'cert-deploy-iis deploy --site %s' to deploy certificate\n", siteName)

	return nil
}

// runDeploy 执行部署
func runDeploy(cmd *cobra.Command, args []string) error {
    logger.Info("starting deployment", "site", siteName)

	// 1. 加载站点配置
	siteConfig, err := cfgManager.LoadSite(siteName)
	if err != nil {
		return fmt.Errorf("failed to load site configuration: %w", err)
	}

    if !siteConfig.Enabled {
        logger.Info("site is disabled, skipping", "site", siteName)
        return nil
    }

    // 2. 检查是否需要续期
	if !force && !siteConfig.NeedsRenewal() {
		days := siteConfig.DaysUntilExpiry()
		logger.Info("certificate does not need renewal",
			"site", siteName,
			"days_until_expiry", days)
		fmt.Printf("✓ Certificate for %s is valid for %d more days\n", siteName, days)
        return nil
    }

    // 2.1 获取当前绑定的证书指纹（用于部署成功后清理旧证书）
    var oldThumbprint string
    if info, e := scanner.NewIISScanner().GetSiteByName(siteName); e == nil {
        oldThumbprint = info.CertThumbprint
    }

    // 3. 从 API 获取证书
    logger.Info("fetching certificate from API", "site", siteName)
    fmt.Printf("→ Fetching certificate from API...\n")

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	f := fetcher.New(30 * time.Second)
	certData, err := f.Info(ctx, siteConfig.API.URL, siteConfig.API.ReferID)
	if err != nil {
		return fmt.Errorf("failed to fetch certificate: %w", err)
	}

	// 检查证书状态
	if certData.Status != "active" {
		logger.Warn("certificate not active",
			"site", siteName,
			"status", certData.Status)
		fmt.Printf("⚠ Certificate status: %s (not active, will retry later)\n", certData.Status)

		// 更新重试时间
		siteConfig.Metadata.LastCheckAt = time.Now()
		cfgManager.SaveSite(siteConfig)
		return nil
	}

	logger.Info("certificate fetched", "site", siteName, "status", certData.Status)
	fmt.Printf("✓ Certificate fetched successfully\n")

	// 4. 验证证书
	fmt.Printf("→ Validating certificate...\n")

	// 解析证书
	block, _ := pem.Decode([]byte(certData.Cert))
	if block == nil {
		return fmt.Errorf("failed to decode certificate PEM")
	}

	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return fmt.Errorf("failed to parse certificate: %w", err)
	}

	// 验证域名覆盖
	domainValidator := validator.NewDomainValidator(
		siteConfig.Domains,
		siteConfig.Validation.IgnoreDomainMismatch,
	)

	if err := domainValidator.ValidateDomainCoverage(cert); err != nil {
		return fmt.Errorf("domain validation failed: %w", err)
	}

	logger.Info("certificate validated",
		"site", siteName,
		"subject", cert.Subject.CommonName,
		"expires_at", cert.NotAfter)
	fmt.Printf("✓ Certificate validated\n")

	// 5. 备份旧证书信息 (IIS 记录证书信息,无文件备份)
	if siteConfig.Backup.Enabled {
		fmt.Printf("→ Recording certificate info...\n")
		logger.Info("certificate info recorded",
			"subject", cert.Subject.CommonName,
			"serial", fmt.Sprintf("%X", cert.SerialNumber))
		fmt.Printf("✓ Certificate info recorded\n")
	}

    // 6. 部署证书到 IIS
    fmt.Printf("→ Deploying certificate to IIS...\n")

	iisDeployer := deployer.NewIISDeployer(
		siteConfig.IIS.SiteName,
		siteConfig.IIS.Hostname,
		siteConfig.IIS.Port,
		siteConfig.IIS.IP,
	)

	// 读取本地私钥用于 PFX 生成
	keyContent, err := os.ReadFile(siteConfig.Paths.PrivateKey)
	if err != nil { return fmt.Errorf("read private key failed: %w", err) }
	if err := iisDeployer.Deploy(certData.Cert, certData.IntermediateCert, string(keyContent)); err != nil {
		return fmt.Errorf("deployment failed: %w", err)
	}

    // 6.1 清理旧证书（按策略：部署成功后移除旧的 thumbprint）
    if strings.TrimSpace(oldThumbprint) != "" {
        if err := iisDeployer.RemoveOldCertificate(oldThumbprint); err != nil {
            logger.Warn("failed to remove old certificate", "error", err)
        }
    }

	logger.Info("certificate deployed", "site", siteName)
	fmt.Printf("✓ Certificate deployed successfully\n")

	// 7. 更新元数据
	siteConfig.Metadata.LastDeployAt = time.Now()
	siteConfig.Metadata.LastCheckAt = time.Now()
	siteConfig.Metadata.CertExpiresAt = cert.NotAfter
	siteConfig.Metadata.CertSerial = fmt.Sprintf("%X", cert.SerialNumber)

	if err := cfgManager.SaveSite(siteConfig); err != nil {
		logger.Warn("failed to update metadata", "error", err)
	}

	fmt.Printf("\n✓ Deployment completed for %s\n", siteName)
	fmt.Printf("  Certificate expires: %s\n", cert.NotAfter.Format("2006-01-02 15:04:05"))
	fmt.Printf("  Days until expiry: %d\n", siteConfig.DaysUntilExpiry())

	return nil
}

// runStatus 执行状态查看
func runStatus(cmd *cobra.Command, args []string) error {
	sites, err := cfgManager.ListSites()
	if err != nil {
		return fmt.Errorf("failed to list sites: %w", err)
	}

	if len(sites) == 0 {
		fmt.Println("No sites configured yet.")
		fmt.Println("Run 'cert-deploy-iis scan' to discover sites")
		fmt.Println("Run 'cert-deploy-iis init' to configure a site")
		return nil
	}

	fmt.Printf("Found %d configured site(s):\n\n", len(sites))

	for i, site := range sites {
		fmt.Printf("%d. %s\n", i+1, site.SiteName)
		fmt.Printf("   Status: %s\n", map[bool]string{true: "Enabled", false: "Disabled"}[site.Enabled])
		fmt.Printf("   Hostname: %s\n", site.IIS.Hostname)
		fmt.Printf("   API: %s\n", site.API.URL)

		if !site.Metadata.CertExpiresAt.IsZero() {
			days := site.DaysUntilExpiry()
			status := "OK"
			if days < 7 {
				status = "CRITICAL"
			} else if days < 15 {
				status = "WARNING"
			}
			fmt.Printf("   Certificate Expiry: %s (%d days left, %s)\n",
				site.Metadata.CertExpiresAt.Format("2006-01-02"), days, status)
		}

		fmt.Println()
	}

	return nil
}

// runCheck 执行检查
func runCheck(cmd *cobra.Command, args []string) error {
	sites, err := cfgManager.ListSites()
	if err != nil {
		return fmt.Errorf("failed to list sites: %w", err)
	}

	if len(sites) == 0 {
		fmt.Println("No sites configured.")
		return nil
	}

	hasExpiring := false

    fmt.Println("Certificate Expiration Check:")

	for _, site := range sites {
		if !site.Enabled {
			continue
		}

		if site.Metadata.CertExpiresAt.IsZero() {
			fmt.Printf("  %s: NO CERTIFICATE\n", site.SiteName)
			continue
		}

		daysLeft := site.DaysUntilExpiry()

		if daysLeft <= site.Schedule.RenewBeforeDays {
			hasExpiring = true
			fmt.Printf("  %s: NEEDS RENEWAL (expires in %d days)\n", site.SiteName, daysLeft)
		} else {
			fmt.Printf("  %s: OK (expires in %d days)\n", site.SiteName, daysLeft)
		}
	}

	if hasExpiring {
		fmt.Println("\nSome certificates need renewal. Run 'cert-deploy-iis deploy --site <site>' to renew.")
	}

	return nil
}

// runDaemon 执行守护进程
func runDaemon(cmd *cobra.Command, args []string) error {
    fmt.Println("Starting cert-deploy-iis daemon...")
    fmt.Printf("Version: %s\n", version)
    fmt.Printf("Working directory: %s\n", cfgManager.GetWorkDir())

    d := daemon.New(cfgManager, logger)
    return d.Run()
}

// setupLogger 设置日志
func setupLogger() *slog.Logger {
    opts := &slog.HandlerOptions{Level: slog.LevelInfo}
    handler := slog.NewJSONHandler(os.Stdout, opts)
    return slog.New(handler)
}
